package com.lan.evalulate;

import com.lan.ScriptContext;
import com.lan.antlr.ScriptBaseVisitor;
import com.lan.antlr.ScriptParser;
import com.lan.model.Symbol;
import com.lan.model.Value;
import com.lan.model.Vv;
import com.lan.model.symbol.Variable;
import com.lan.script.BreakOt;
import com.lan.script.FunctionOt;
import com.lan.script.ReturnOt;
import com.lan.script.ScriptOt;

import java.util.Stack;

/**
 * 在AST上解释执行脚本
 */
public class AstEvaluator extends ScriptBaseVisitor<Object> {

    private ScriptContext context;


    protected boolean traceStackFrame = false;

    protected boolean traceFunctionCall = false;

    ///////////////////////////////////////////////////////////
    /// 栈桢的管理
    private Stack<StackFrame> stack = new Stack<>();

    public AstEvaluator(ScriptContext context) {
        this.context = context;
    }


    private void popStack() {
        stack.pop();
    }

    private void dumpStackFrame() {
        System.out.println("\nStack Frames ----------------");
        for (StackFrame frame : stack) {
            System.out.println(frame);
        }
        System.out.println("-----------------------------\n");
    }


    /**
     * 栈桢入栈。
     * 其中最重要的任务，是要保证栈桢的parent设置正确。否则，
     * (1)随着栈的变深，查找变量的性能会降低；
     * (2)甚至有可能找错栈桢，比如在递归(直接或间接)的场景下。
     *
     * @param frame
     */
    private void pushStack(StackFrame frame) {
        if (stack.size() > 0) {
            //从栈顶到栈底依次查找
            for (int i = stack.size() - 1; i > 0; i--) {
                StackFrame f = stack.get(i);

                if (f.getScope().getScope() == frame.getScope().getScope()) {
                    /*
                        如果新加入的栈桢，跟某个已有的栈桢的scope是一样的，那么这俩的parentFrame也一样。
                        因为它们原本就是同一级的嘛。
                        比如：
                        void foo(){};
                        void bar(foo());

                        或者：
                        void foo();
                        if (...){
                            foo();
                        }
                    */
                    frame.setParent(f.getParent());
                    break;
                } else if (f.getScope() == frame.getScope().getScope()) {
                      /*
                        如果新加入的栈桢，是某个已有的栈桢的下一级，那么就把把这个父子关系建立起来。比如：
                        void foo(){
                            if (...){  //把这个块往栈桢里加的时候，就符合这个条件。
                            }
                        }
                        再比如,下面的例子:
                        class MyClass{
                            void foo();
                        }
                        MyClass c = MyClass();  //先加Class的栈桢，里面有类的属性，包括父类的
                        c.foo();                //再加foo()的栈桢
                     */
                    frame.setParent(f);
                    break;
                } else if (frame.getObject() instanceof FunctionOt) {
                     /*
                        这是针对函数可能是一等公民的情况。这个时候，函数运行时的作用域，与声明时的作用域会不一致。
                        我在这里设计了一个“receiver”的机制，意思是这个函数是被哪个变量接收了。要按照这个receiver的作用域来判断。
                         */
                    FunctionOt functionObject = (FunctionOt) frame.getObject();
                    if (functionObject.getFunction() != null && functionObject.getFunction().getScope() == f.getScope()) {
                        frame.setParent(f);
                        break;
                    }
                }
            }

            if (frame.getParent() == null) {
                frame.setParent(stack.peek());
            }
        }
        stack.push(frame);
        if (traceStackFrame) {
            dumpStackFrame();
        }
    }

    public Value getValue(Variable variable) {
        StackFrame f = stack.peek();
        ScriptOt o = null;
        while (f != null) {
            if (f.getScope().contains(variable)) {
                //对于对象来说，会查找所有父类的属性
                o = f.getObject();
                break;
            }
            f = f.getParent();
        }
        //通过正常的作用域找不到，就从闭包里找
        //原理：ScriptObject中可能有一些变量，其作用域跟StackFrame.scope是不同的。
        if (o == null) {
            f = stack.peek();
            while (f != null) {
                if (f.contains(variable)) {
                    o = f.getObject();
                    break;
                }
                f = f.getParent();
            }
        }
        return new Vv(variable, o);
    }


    @Override
    public Object visitProg(ScriptParser.ProgContext ctx) {
        Object rtn = null;
        pushStack(new StackFrame(context.getNs()));
        rtn = visitBlockStatements(ctx.blockStatements());
        popStack();
        return rtn;
    }

    @Override
    public Object visitBlockStatements(ScriptParser.BlockStatementsContext ctx) {
        Object rtn = null;
        for (ScriptParser.BlockStatementContext child : ctx.blockStatement()) {
            rtn = visitBlockStatement(child);
            if (rtn instanceof BreakOt) {
                //如果返回的是break，那么不执行下面的语句
                break;
            } else if (rtn instanceof ReturnOt) {
                //碰到Return, 退出函数
                // TODO 要能层层退出一个个block，弹出一个栈桢
                break;
            }
        }
        return rtn;
    }

    @Override
    public Object visitBlockStatement(ScriptParser.BlockStatementContext ctx) {
        Object rtn = null;
        if (ctx.variableDeclarator() != null) {
            rtn = visitVariableDeclarator(ctx.variableDeclarator());
        } else if (ctx.statement() != null) {
            rtn = visitStatement(ctx.statement());
        }
        return rtn;
    }


    @Override
    public Object visitVariableDeclarator(ScriptParser.VariableDeclaratorContext ctx) {
        Object rtn = null;
        Value value = (Value) visitVariableDeclaratorId(ctx.variableDeclaratorId());
        if (ctx.variableInitializer() != null) {
            rtn = visitVariableInitializer(ctx.variableInitializer());
            if (rtn instanceof Value) {
                rtn = ((Value) rtn).getValue();
            }
            value.setValue(rtn);
        }
        return null;
    }


    @Override
    public Object visitVariableDeclaratorId(ScriptParser.VariableDeclaratorIdContext ctx) {
        Object rtn = null;
        Symbol symbol = context.lookupVariable(null, null);


        return null;
    }
}





















